#region Copyright
/// <copyright>
/// Copyright (c) 2007 Ilia Shramko.
/// Released under the terms of the GNU General Public License.
/// </copyright>
#endregion

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Text;
using System.Threading;

namespace OshHost
{
	public class HtmlWriterTraceListener : TraceListener
	{
		private Host _host;
		private List<string> _buffer = new List<string>();
		private const int MaxBufferSize = 1000;
		private const int FailEventId = 0;
		private const int WriteEventId = 1;
		private Timer _bufferFlushTimer;
		private TimeSpan BufferFlushTimeout = TimeSpan.FromSeconds(1);
		private bool _flushing;

		public HtmlWriterTraceListener() : this(null)
		{
		}

		public HtmlWriterTraceListener(string name) : base(name)
		{
			_bufferFlushTimer = new Timer(new TimerCallback(OnBufferFlushTimeout));
			_bufferFlushTimer.Change(BufferFlushTimeout, BufferFlushTimeout);
		}

		public override void TraceData(TraceEventCache eventCache, string source,
			TraceEventType eventType, int id, object data)
		{
			TraceData(eventCache, source, eventType, id, data);
		}

		public override void TraceData(TraceEventCache eventCache, string source,
			TraceEventType eventType, int id, params object[] data)
		{
			if(this.Filter == null || this.Filter.ShouldTrace(eventCache, source, eventType, id, null, null, null, data))
			{
				StringBuilder sb = new StringBuilder();
				if(data != null)
				{
					for(int i = 0; i < data.Length; i++)
					{
						if(i != 0)
							sb.Append(", ");

						if(data[i] != null)
							sb.Append(data[i].ToString());
					}
				}
				WriteEvent(eventCache, source, eventType, id, sb.ToString());
			}
		}

		public override void TraceEvent(TraceEventCache eventCache, string source,
			TraceEventType eventType, int id)
		{
			TraceEvent(eventCache, source, eventType, id, string.Empty, null);
		}

		public override void TraceEvent(TraceEventCache eventCache, string source,
			TraceEventType eventType, int id, string message)
		{
			TraceEvent(eventCache, source, eventType, id, message, null);
		}

		public override void TraceEvent(TraceEventCache eventCache, string source,
			TraceEventType eventType, int id, string format, params object[] args)
		{
			if(this.Filter == null || this.Filter.ShouldTrace(eventCache, source, eventType, id, format, args, null, null))
			{
				string message;
				if(args != null)
					message = string.Format(CultureInfo.InvariantCulture, format, args);
				else
					message = format;

				WriteEvent(eventCache, source, eventType, id, message);
			}
		}

		public override void TraceTransfer(TraceEventCache eventCache, string source,
			int id, string message, Guid relatedActivityId)
		{
			TraceEvent(eventCache, source, TraceEventType.Transfer, id,
				message + ", relatedActivityId=" + relatedActivityId.ToString());
		}

		public override void Fail(string message, string detailMessage)
		{
			TraceEvent(null, null, TraceEventType.Error, FailEventId,
				message + " " + (detailMessage == null ? string.Empty : detailMessage));
		}
 
		public override void Write(string message)
		{
			WriteLine(message);
		}

		public override void WriteLine(string message)
		{
			WriteEvent(null, null, TraceEventType.Verbose, WriteEventId, message);
		}

		private void WriteEvent(TraceEventCache eventCache, string source,
			TraceEventType eventType, int id, string message)
		{
			StringBuilder sb = new StringBuilder();
			sb.Append("<div class='event ");
			sb.Append(GetEventTypeClass(eventType));
			sb.Append(" ");
			if(!string.IsNullOrEmpty(source))
				sb.Append(source.Replace('.', '-'));
			sb.Append("'>");

			// Write source
			sb.Append("<span class='source'>");
			sb.Append(source);
			sb.Append("</span>");

			// Write event id
			sb.Append("<span class='id'>");
			sb.Append(id.ToString(CultureInfo.InvariantCulture));
			sb.Append("</span>");

			// Write datetime
			sb.Append("<span class='time'>");
			//if(IsEnabled(TraceOptions.DateTime))
			{
				if(eventCache != null)
					sb.Append(eventCache.DateTime.ToLocalTime().TimeOfDay.ToString());
				//sb.Append(eventCache.DateTime.ToString("o", CultureInfo.InvariantCulture));
			}
			sb.Append("</span>");

			// Write timestamp
			sb.Append("<span class='timestamp'>");
			//if(IsEnabled(TraceOptions.Timestamp))
			{
				if(eventCache != null)
					sb.Append(eventCache.Timestamp.ToString(CultureInfo.InvariantCulture));
			}
			sb.Append("</span>");

			// Write thread id
			sb.Append("<span class='threadid'>");
			//if(IsEnabled(TraceOptions.ThreadId))
			{
				if(eventCache != null)
					sb.Append(eventCache.ThreadId);
			}
			sb.Append("</span>");

			// Write message
			sb.Append("<span class='message'>");
			sb.Append(System.Web.HttpUtility.HtmlEncode(message));
			sb.Append("</span>");

			sb.Append("</div>");

			message = sb.ToString();

			string str = "\"" +
				message.Replace("\\", "\\\\").
				Replace("\"", "\"\"").
				Replace("\r", "\\r").
				Replace("\n", "\\n") + "\"";
			Enqueue(str);
		}

		private bool IsEnabled(TraceOptions opts)
		{
			return ((opts & this.TraceOutputOptions) != TraceOptions.None);
		}

		private string GetEventTypeClass(TraceEventType eventType)
		{
			return eventType.ToString().ToLowerInvariant();
		}

		private void OnBufferFlushTimeout(object state)
		{
			if(!EnsureWriter())
				return;

			if(_flushing)
				return;

			string events = null;
			lock(this)
			{
				if(_flushing)
					return;
				_flushing = true;

				if(_buffer.Count > 0)
				{
					events = string.Join(",", _buffer.ToArray());
					_buffer.Clear();
				}
			}

			try
			{
				if(!string.IsNullOrEmpty(events))
				{
					string code = "writeEvents([" + events + "]);";
					this.Host.TraceWebBrowser.InvokeScript("eval", code);
				}
			}
			finally
			{
				_flushing = false;
			}
		}

		public override void Flush()
		{
			// Do not flush, it happens too often
		}

		private void Enqueue(string code)
		{
			lock(this)
			{
				if(_buffer.Count > MaxBufferSize)
					_buffer.RemoveAt(0);
				_buffer.Add(code);
			}
		}

		private bool EnsureWriter()
		{
			return (this.Host.TraceWebBrowser != null);
		}

		private Host Host
		{
			get
			{
				if(_host == null)
					_host = Host.Current;
				return _host;
			}
		}
	}
}
